<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0,viewport-fit=cover"><title>ShardingSphere-inline表达式线程安全问题定位 | flyingzc's blog</title><meta name="author" content="flyingzc"><meta name="copyright" content="flyingzc"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="ShardingSphere inline表达式线程安全问题定位问题背景春节期间发现 ShardingSphere 事务 E2E 偶发执行失败问题，并且每次执行失败需要执行很久，直到超时。最终定位发现 inline 表达式存在线程安全问题。本文记录定位并解决 inline 表达式线程安全问题的过程。 问题原因1.GroovyInlineExpressionParser 里有成员变量，存在并发修改，">
<meta property="og:type" content="article">
<meta property="og:title" content="ShardingSphere-inline表达式线程安全问题定位">
<meta property="og:url" content="https://flyingzc.github.io/articles/shardingsphere/ShardingSphere-inline%E8%A1%A8%E8%BE%BE%E5%BC%8F%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E9%97%AE%E9%A2%98%E5%AE%9A%E4%BD%8D.html">
<meta property="og:site_name" content="flyingzc&#39;s blog">
<meta property="og:description" content="ShardingSphere inline表达式线程安全问题定位问题背景春节期间发现 ShardingSphere 事务 E2E 偶发执行失败问题，并且每次执行失败需要执行很久，直到超时。最终定位发现 inline 表达式存在线程安全问题。本文记录定位并解决 inline 表达式线程安全问题的过程。 问题原因1.GroovyInlineExpressionParser 里有成员变量，存在并发修改，">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://flyingzc.github.io/images/zc-icon.jpeg">
<meta property="article:published_time" content="2024-02-26T16:00:00.000Z">
<meta property="article:modified_time" content="2025-01-21T01:51:41.214Z">
<meta property="article:author" content="flyingzc">
<meta property="article:tag" content="ShardingSphere">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://flyingzc.github.io/images/zc-icon.jpeg"><script type="application/ld+json">{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "ShardingSphere-inline表达式线程安全问题定位",
  "url": "https://flyingzc.github.io/articles/shardingsphere/ShardingSphere-inline%E8%A1%A8%E8%BE%BE%E5%BC%8F%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E9%97%AE%E9%A2%98%E5%AE%9A%E4%BD%8D.html",
  "image": "https://flyingzc.github.io/images/zc-icon.jpeg",
  "datePublished": "2024-02-26T16:00:00.000Z",
  "dateModified": "2025-01-21T01:51:41.214Z",
  "author": [
    {
      "@type": "Person",
      "name": "flyingzc",
      "url": "https://flyingzc.github.io/"
    }
  ]
}</script><link rel="shortcut icon" href="/images/zc-icon.jpeg"><link rel="canonical" href="https://flyingzc.github.io/articles/shardingsphere/ShardingSphere-inline%E8%A1%A8%E8%BE%BE%E5%BC%8F%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E9%97%AE%E9%A2%98%E5%AE%9A%E4%BD%8D.html"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><meta name="google-site-verification" content="DentpX3LnP_WNgEeFnNIcTZZky-Bby-zL-uI_9GqTAw"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css"><script>
    (() => {
      
    const saveToLocal = {
      set: (key, value, ttl) => {
        if (!ttl) return
        const expiry = Date.now() + ttl * 86400000
        localStorage.setItem(key, JSON.stringify({ value, expiry }))
      },
      get: key => {
        const itemStr = localStorage.getItem(key)
        if (!itemStr) return undefined
        const { value, expiry } = JSON.parse(itemStr)
        if (Date.now() > expiry) {
          localStorage.removeItem(key)
          return undefined
        }
        return value
      }
    }

    window.btf = {
      saveToLocal,
      getScript: (url, attr = {}) => new Promise((resolve, reject) => {
        const script = document.createElement('script')
        script.src = url
        script.async = true
        Object.entries(attr).forEach(([key, val]) => script.setAttribute(key, val))
        script.onload = script.onreadystatechange = () => {
          if (!script.readyState || /loaded|complete/.test(script.readyState)) resolve()
        }
        script.onerror = reject
        document.head.appendChild(script)
      }),
      getCSS: (url, id) => new Promise((resolve, reject) => {
        const link = document.createElement('link')
        link.rel = 'stylesheet'
        link.href = url
        if (id) link.id = id
        link.onload = link.onreadystatechange = () => {
          if (!link.readyState || /loaded|complete/.test(link.readyState)) resolve()
        }
        link.onerror = reject
        document.head.appendChild(link)
      }),
      addGlobalFn: (key, fn, name = false, parent = window) => {
        if (!false && key.startsWith('pjax')) return
        const globalFn = parent.globalFn || {}
        globalFn[key] = globalFn[key] || {}
        globalFn[key][name || Object.keys(globalFn[key]).length] = fn
        parent.globalFn = globalFn
      }
    }
  
      
      const activateDarkMode = () => {
        document.documentElement.setAttribute('data-theme', 'dark')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
        }
      }
      const activateLightMode = () => {
        document.documentElement.setAttribute('data-theme', 'light')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
        }
      }

      btf.activateDarkMode = activateDarkMode
      btf.activateLightMode = activateLightMode

      const theme = saveToLocal.get('theme')
    
          theme === 'dark' ? activateDarkMode() : theme === 'light' ? activateLightMode() : null
        
      
      const asideStatus = saveToLocal.get('aside-status')
      if (asideStatus !== undefined) {
        document.documentElement.classList.toggle('hide-aside', asideStatus === 'hide')
      }
    
      
    const detectApple = () => {
      if (/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)) {
        document.documentElement.classList.add('apple')
      }
    }
    detectApple()
  
    })()
  </script><script async="async" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script><script>(adsbygoogle = window.adsbygoogle || []).push({
  google_ad_client: '',
  enable_page_level_ads: 'true'
});</script><script>const GLOBAL_CONFIG = {
  root: '/',
  algolia: undefined,
  localSearch: undefined,
  translate: undefined,
  highlight: {"plugin":"highlight.js","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":false,"highlightFullpage":false,"highlightMacStyle":false},
  copy: {
    success: '复制成功',
    error: '复制失败',
    noSupport: '浏览器不支持'
  },
  relativeDate: {
    homepage: false,
    post: false
  },
  runtime: '',
  dateSuffix: {
    just: '刚刚',
    min: '分钟前',
    hour: '小时前',
    day: '天前',
    month: '个月前'
  },
  copyright: undefined,
  lightbox: 'null',
  Snackbar: undefined,
  infinitegrid: {
    js: 'https://cdn.jsdelivr.net/npm/@egjs/infinitegrid/dist/infinitegrid.min.js',
    buttonText: '加载更多'
  },
  isPhotoFigcaption: false,
  islazyloadPlugin: false,
  isAnchor: false,
  percent: {
    toc: true,
    rightside: false,
  },
  autoDarkmode: false
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
  title: 'ShardingSphere-inline表达式线程安全问题定位',
  isHighlightShrink: false,
  isToc: true,
  pageType: 'post'
}</script><link rel="stylesheet" href="/self/github-dark.css"><meta name="generator" content="Hexo 7.0.0"></head><body><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img text-center"><img src="/images/zc-icon.jpeg" onerror="this.onerror=null;this.src='/img/friend_404.gif'" alt="avatar"/></div><div class="site-data text-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">84</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">36</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">29</div></a></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 主页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 归档</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></div><div class="menus_item"><a class="site-page" href="/about/"><i class="fa-fw fas fa-heart"></i><span> 关于我</span></a></div></div></div></div><div class="post" id="body-wrap"><header class="post-bg" id="page-header"><nav id="nav"><span id="blog-info"><a class="nav-site-title" href="/"><span class="site-name">flyingzc's blog</span></a><a class="nav-page-title" href="/"><span class="site-name">ShardingSphere-inline表达式线程安全问题定位</span></a></span><div id="menus"><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 主页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 归档</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></div><div class="menus_item"><a class="site-page" href="/about/"><i class="fa-fw fas fa-heart"></i><span> 关于我</span></a></div></div><div id="toggle-menu"><span class="site-page"><i class="fas fa-bars fa-fw"></i></span></div></div></nav><div id="post-info"><h1 class="post-title">ShardingSphere-inline表达式线程安全问题定位</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="far fa-calendar-alt fa-fw post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" datetime="2024-02-26T16:00:00.000Z" title="发表于 2024-02-27 00:00:00">2024-02-27</time><span class="post-meta-separator">|</span><i class="fas fa-history fa-fw post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" datetime="2025-01-21T01:51:41.214Z" title="更新于 2025-01-21 09:51:41">2025-01-21</time></span><span class="post-meta-categories"><span class="post-meta-separator">|</span><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/ShardingSphere/">ShardingSphere</a></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-pv-cv" id="" data-flag-title=""><i class="far fa-eye fa-fw post-meta-icon"></i><span class="post-meta-label">浏览量:</span><span id="busuanzi_value_page_pv"><i class="fa-solid fa-spinner fa-spin"></i></span></span></div></div></div></header><main class="layout" id="content-inner"><div id="post"><article class="container post-content" id="article-container"><h1 id="ShardingSphere-inline表达式线程安全问题定位"><a href="#ShardingSphere-inline表达式线程安全问题定位" class="headerlink" title="ShardingSphere inline表达式线程安全问题定位"></a>ShardingSphere inline表达式线程安全问题定位</h1><h2 id="问题背景"><a href="#问题背景" class="headerlink" title="问题背景"></a>问题背景</h2><p>春节期间发现 ShardingSphere 事务 E2E 偶发执行失败问题，并且每次执行失败需要执行很久，直到超时。最终定位发现 inline 表达式存在线程安全问题。本文记录定位并解决 inline 表达式线程安全问题的过程。</p>
<h2 id="问题原因"><a href="#问题原因" class="headerlink" title="问题原因"></a>问题原因</h2><p>1.<code>GroovyInlineExpressionParser</code> 里有成员变量，存在并发修改，不能使用单例 SPI 实现;<br>2.执行 Groovy 表达式时，需要执行 rehydrate 方法 copy Closure,使得每个 Closure 都有独立的执行环境，避免属性赋值时产生线程安全问题。</p>
<h2 id="问题定位"><a href="#问题定位" class="headerlink" title="问题定位"></a>问题定位</h2><h3 id="构造测试用例尝试复现问题"><a href="#构造测试用例尝试复现问题" class="headerlink" title="构造测试用例尝试复现问题"></a>构造测试用例尝试复现问题</h3><p>构造测试用例，且在测试用例中添加线程相关信息，观察执行结果。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Test</span><br><span class="hljs-meta">@SneakyThrows(&#123;ExecutionException.class, InterruptedException.class&#125;)</span><br><span class="hljs-keyword">void</span> <span class="hljs-title function_">assertThreadSafety</span><span class="hljs-params">()</span> &#123;<br>    <span class="hljs-type">int</span> <span class="hljs-variable">threadCount</span> <span class="hljs-operator">=</span> <span class="hljs-number">2</span>;<br>    <span class="hljs-type">ExecutorService</span> <span class="hljs-variable">pool</span> <span class="hljs-operator">=</span> Executors.newFixedThreadPool(threadCount);<br>    List&lt;Future&lt;?&gt;&gt; futures = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;(threadCount);<br>    <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i &lt; threadCount; i++) &#123;<br>        Future&lt;?&gt; future = pool.submit(<span class="hljs-built_in">this</span>::createInlineExpressionParseTask);<br>        futures.add(future);<br>    &#125;<br>    <span class="hljs-keyword">for</span> (Future&lt;?&gt; future : futures) &#123;<br>        future.get();<br>    &#125;<br>    pool.shutdown();<br>&#125;<br><br><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">createInlineExpressionParseTask</span><span class="hljs-params">()</span> &#123;<br>    <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">j</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; j &lt; <span class="hljs-number">5</span>; j++) &#123;<br>        <span class="hljs-comment">// 传入线程信息</span><br>        <span class="hljs-type">String</span> <span class="hljs-variable">resultSuffix</span> <span class="hljs-operator">=</span> Thread.currentThread().getName() + <span class="hljs-string">&quot;--&quot;</span> + j;<br>        <span class="hljs-type">String</span> <span class="hljs-variable">actual</span> <span class="hljs-operator">=</span> TypedSPILoader.getService(InlineExpressionParser.class, <span class="hljs-string">&quot;GROOVY&quot;</span>, PropertiesBuilder.build(<br>                <span class="hljs-keyword">new</span> <span class="hljs-title class_">PropertiesBuilder</span>.Property(InlineExpressionParser.INLINE_EXPRESSION_KEY, <span class="hljs-string">&quot;ds_$&#123;id%2&#125;&quot;</span>))).evaluateWithArgs(Collections.singletonMap(<span class="hljs-string">&quot;id&quot;</span>, <span class="hljs-number">1</span>));<br>        <span class="hljs-comment">// 断言执行出来的结果是不是当前线程的</span><br>        assertThat(actual, is(String.format(<span class="hljs-string">&quot;ds_%s&quot;</span>, resultSuffix)));<br>        <span class="hljs-type">String</span> <span class="hljs-variable">actual2</span> <span class="hljs-operator">=</span> TypedSPILoader.getService(InlineExpressionParser.class, <span class="hljs-string">&quot;GROOVY&quot;</span>, PropertiesBuilder.build(<br>                <span class="hljs-keyword">new</span> <span class="hljs-title class_">PropertiesBuilder</span>.Property(InlineExpressionParser.INLINE_EXPRESSION_KEY, <span class="hljs-string">&quot;account_$&#123;id&#125;&quot;</span>))).evaluateWithArgs(Collections.singletonMap(<span class="hljs-string">&quot;id&quot;</span>, resultSuffix));<br>        assertThat(actual2, is(String.format(<span class="hljs-string">&quot;account_%s&quot;</span>, resultSuffix)));<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>
<p>通过修改线程数测试几组，结果如下：<br>1: 100个线程，一个线程里一个inline表达式，报错<br>2: 1个线程，一个线程里两个不同inline表达式，没问题<br>3: 2个线程，一个线程里两个不同inline表达式，报错</p>
<p>那么只需要最少两个线程并发执行即可稳定复现这个 bug。</p>
<h3 id="对象属性线程间共享问题"><a href="#对象属性线程间共享问题" class="headerlink" title="对象属性线程间共享问题"></a>对象属性线程间共享问题</h3><p>初步看代码，<code>GroovyInlineExpressionParser</code> 之前是单例的，线程之间会共享同一个实例，<code>GroovyInlineExpressionParser</code> 类中发现有一处成员变量 <code>inlineExpression</code> 多个线程中共享，并发修改的话会导致数据不正确。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// InlineExpressionParser 的 spi 之前是单例的，所有线程对于同一个类型的 spi 实现，共享同一个对象。</span><br><span class="hljs-meta">@SingletonSPI</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">InlineExpressionParser</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">TypedSPI</span> &#123;&#125;<br><br><span class="hljs-comment">// GroovyInlineExpressionParser 实现</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">GroovyInlineExpressionParser</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">InlineExpressionParser</span> &#123;<br>    <br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">String</span> <span class="hljs-variable">INLINE_EXPRESSION_KEY</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;inlineExpression&quot;</span>;<br>    <br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> Map&lt;String, Script&gt; SCRIPTS = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ConcurrentHashMap</span>&lt;&gt;();<br>    <br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">GroovyShell</span> <span class="hljs-variable">SHELL</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">GroovyShell</span>();<br>    <br>    <span class="hljs-comment">// 可以看到此处是成员变量，多个线程之间会串。</span><br>    <span class="hljs-keyword">private</span> String inlineExpression;<br>    <br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">init</span><span class="hljs-params">(<span class="hljs-keyword">final</span> Properties props)</span> &#123;<br>        inlineExpression = props.getProperty(INLINE_EXPRESSION_KEY);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>
<p>这个问题修复起来很简单，移除 @SingletonSPI 声明，每次创建新的实例即可。</p>
<h3 id="添加日志观察执行流程"><a href="#添加日志观察执行流程" class="headerlink" title="添加日志观察执行流程"></a>添加日志观察执行流程</h3><p>上述问题修复后，继续执行测试用例，发现还是会报错。报错日志如下：<br><img src="/images/img-20240226151653.png"><br>看起来就是当 thread2 获取结果之前， thread1 执行了 inline 表达式，thread2 拿到了 thread1 执行的结果。<br>通过日志也能看出来 thread2 执行报错时 <code>[pool-1-thread-2] ERROR</code> 拿到了线程1的执行结果<code>result:ds_pool-1-thread-1--0</code>。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs log">15:12:29.630 [pool-1-thread-2] INFO org.apache.shardingsphere.infra.expr.groovy.GroovyInlineExpressionParser - inlineExpression:ds_$&#123;id&#125;, this:2086977125, closure:1142533146, map:&#123;id=pool-1-thread-2--0&#125;<br>15:12:29.689 [pool-1-thread-2] INFO org.apache.shardingsphere.infra.expr.groovy.GroovyInlineExpressionParserTest - ds_id, j:0,result:ds_pool-1-thread-2--0<br>15:12:29.689 [pool-1-thread-1] INFO org.apache.shardingsphere.infra.expr.groovy.GroovyInlineExpressionParser - inlineExpression:ds_$&#123;id&#125;, this:629624949, closure:595824174, map:&#123;id=pool-1-thread-1--0&#125;<br>15:12:29.689 [pool-1-thread-1] INFO org.apache.shardingsphere.infra.expr.groovy.GroovyInlineExpressionParserTest - ds_id, j:0,result:ds_pool-1-thread-1--0<br>15:12:29.703 [pool-1-thread-2] INFO org.apache.shardingsphere.infra.expr.groovy.GroovyInlineExpressionParser - inlineExpression:account_$&#123;id&#125;, this:1499502307, closure:778755431, map:&#123;id=pool-1-thread-2--0&#125;<br>15:12:29.704 [pool-1-thread-2] INFO org.apache.shardingsphere.infra.expr.groovy.GroovyInlineExpressionParserTest - account_id, j:0,result:account_pool-1-thread-2--0<br>15:12:29.705 [pool-1-thread-2] INFO org.apache.shardingsphere.infra.expr.groovy.GroovyInlineExpressionParser - inlineExpression:ds_$&#123;id&#125;, this:1218528180, closure:667406814, map:&#123;id=pool-1-thread-2--1&#125;<br>15:12:29.704 [pool-1-thread-1] INFO org.apache.shardingsphere.infra.expr.groovy.GroovyInlineExpressionParser - inlineExpression:account_$&#123;id&#125;, this:2009787398, closure:1068736255, map:&#123;id=pool-1-thread-1--0&#125;<br>15:12:29.705 [pool-1-thread-1] INFO org.apache.shardingsphere.infra.expr.groovy.GroovyInlineExpressionParserTest - account_id, j:0,result:account_pool-1-thread-1--0<br>15:12:29.705 [pool-1-thread-2] ERROR org.apache.shardingsphere.infra.expr.groovy.GroovyInlineExpressionParserTest - ds_id, j:1,result:ds_pool-1-thread-1--0<br>15:12:29.705 [pool-1-thread-1] INFO org.apache.shardingsphere.infra.expr.groovy.GroovyInlineExpressionParser - inlineExpression:ds_$&#123;id&#125;, this:1072633959, closure:478686467, map:&#123;id=pool-1-thread-1--1&#125;<br>15:12:29.705 [pool-1-thread-1] INFO org.apache.shardingsphere.infra.expr.groovy.GroovyInlineExpressionParserTest - ds_id, j:1,result:ds_pool-1-thread-1--1<br>15:12:29.709 [pool-1-thread-1] INFO org.apache.shardingsphere.infra.expr.groovy.GroovyInlineExpressionParser - inlineExpression:account_$&#123;id&#125;, this:2138009411, closure:1272220298, map:&#123;id=pool-1-thread-1--1&#125;<br>15:12:29.709 [pool-1-thread-1] INFO org.apache.shardingsphere.infra.expr.groovy.GroovyInlineExpressionParserTest - account_id, j:1,result:account_pool-1-thread-1--1<br></code></pre></td></tr></table></figure>

<h3 id="DEBUG模拟复现流程"><a href="#DEBUG模拟复现流程" class="headerlink" title="DEBUG模拟复现流程"></a>DEBUG模拟复现流程</h3><p>通过 debug 控制两个线程执行流程，当 thread1 在 <code>result.call().toString()</code> 执行之前等待 thread2 执行完成后再返回时，thread1 得到的结果不正确。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> String <span class="hljs-title function_">evaluateWithArgs</span><span class="hljs-params">(<span class="hljs-keyword">final</span> Map&lt;String, Comparable&lt;?&gt;&gt; map)</span> &#123;<br>    <span class="hljs-comment">// 1.thread1执行</span><br>    <span class="hljs-comment">// 3.thread2执行</span><br>    Closure&lt;?&gt; result = (Closure&lt;?&gt;) evaluate(<span class="hljs-string">&quot;&#123;it -&gt; \&quot;&quot;</span> + handlePlaceHolder(inlineExpression) + <span class="hljs-string">&quot;\&quot;&#125;&quot;</span>);<br>    log.info(<span class="hljs-string">&quot;inlineExpression:&#123;&#125;, this:&#123;&#125;, closure:&#123;&#125;, map:&#123;&#125;&quot;</span>, inlineExpression, System.identityHashCode(<span class="hljs-built_in">this</span>), System.identityHashCode(result), map);<br>    result.rehydrate(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Expando</span>(), <span class="hljs-literal">null</span>, <span class="hljs-literal">null</span>)<br>            .setResolveStrategy(Closure.DELEGATE_ONLY);<br>    map.forEach(result::setProperty);<br>    <span class="hljs-comment">// 2.thread1在此等待</span><br>    <span class="hljs-comment">// 5.thread1执行完毕返回结果，观察到结果不对</span><br>    <span class="hljs-keyword">return</span> result.call().toString();    <br>    <span class="hljs-comment">// 4.thread2执行完毕返回结果</span><br>&#125;<br></code></pre></td></tr></table></figure>

<p>结合后面的分析也能得出，因为线程之间共享了 context，上面 thread2 执行完把 context 里共享的属性给改了，导致 thread1 执行出现问题。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 原有有问题的代码，只调用了 rehydrate 方法，但是没有获取 rehydrate 方法里 clone 后的结果。所有线程之间共享一个执行环境，导致属性会串。</span><br><span class="hljs-keyword">public</span> String <span class="hljs-title function_">evaluateWithArgs</span><span class="hljs-params">(<span class="hljs-keyword">final</span> Map&lt;String, Comparable&lt;?&gt;&gt; map)</span> &#123;  <br>    Closure&lt;?&gt; result = (Closure&lt;?&gt;) evaluate(<span class="hljs-string">&quot;&#123;it -&gt; \&quot;&quot;</span> + handlePlaceHolder(inlineExpression) + <span class="hljs-string">&quot;\&quot;&#125;&quot;</span>);  <br>    <span class="hljs-comment">// 这里会 copy Closure，返回新的执行环境，但是结果被忽略了</span><br>    result.rehydrate(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Expando</span>(), <span class="hljs-literal">null</span>, <span class="hljs-literal">null</span>)  <br>            .setResolveStrategy(Closure.DELEGATE_ONLY);<br>    <span class="hljs-comment">// 所以这里设置属性时，共用的同一个 context, 线程之间会串</span><br>    map.forEach(result::setProperty);  <br>    <span class="hljs-keyword">return</span> result.call().toString();  <br>&#125;<br><br><span class="hljs-comment">// 修改后的代码如下：获取 copy 的 Closure,那么每个 Closure 使用单独的执行环境。</span><br>Closure&lt;?&gt; result = ((Closure&lt;?&gt;) evaluate(<span class="hljs-string">&quot;&#123;it -&gt; \&quot;&quot;</span> + handlePlaceHolder(inlineExpression) + <span class="hljs-string">&quot;\&quot;&#125;&quot;</span>)).rehydrate(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Expando</span>(), <span class="hljs-literal">null</span>, <span class="hljs-literal">null</span>);<br></code></pre></td></tr></table></figure>

<h2 id="问题分析"><a href="#问题分析" class="headerlink" title="问题分析"></a>问题分析</h2><h3 id="DEBUG分析问题"><a href="#DEBUG分析问题" class="headerlink" title="DEBUG分析问题"></a>DEBUG分析问题</h3><p>由于上面发现执行 <code>evaluateWithArgs</code> 方法会有线程安全问题，所以 DEBUG 分析里面的逻辑。</p>
<p>有问题的代码如下：<br><code>result.rehydrate(new Expando(), null, null)</code> 方法作用：重新实例化一个闭包对象,设置 delegate 为 Expando。这样闭包的执行环境将改变，不再依赖于原始闭包的环境。<br>但是明显下面的代码里没有使用 rehydrate 返回的 copy 的 Closure 对象，最终导致 <code>result::setProperty</code> 设置的属性在线程之间共享了。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs java">Closure&lt;?&gt; result = ((Closure&lt;?&gt;) evaluate(<span class="hljs-string">&quot;&#123;it -&gt; \&quot;&quot;</span> + handlePlaceHolder(inlineExpression) + <span class="hljs-string">&quot;\&quot;&#125;&quot;</span>));<br><span class="hljs-comment">// 有问题的调用代码，没有获取 rehydrate() copy后的 Closure，导致线程之间属性串了。</span><br>result.rehydrate(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Expando</span>(), <span class="hljs-literal">null</span>, <span class="hljs-literal">null</span>);<br>map.forEach(result::setProperty);<br><span class="hljs-keyword">return</span> result.call().toString();    <br><br><span class="hljs-comment">// Closure#rehydrate 方法逻辑如下：</span><br><span class="hljs-keyword">public</span> Closure&lt;V&gt; <span class="hljs-title function_">rehydrate</span><span class="hljs-params">(Object delegate, Object owner, Object thisObject)</span> &#123;<br>    <span class="hljs-comment">// clone 当前 Closure</span><br>    Closure&lt;V&gt; result = (Closure&lt;V&gt;) <span class="hljs-built_in">this</span>.clone();<br>    <span class="hljs-comment">// 设置传入的 Expando</span><br>    result.delegate = delegate;<br>    result.owner = owner;<br>    result.thisObject = thisObject;<br>    <span class="hljs-keyword">return</span> result;<br>&#125;<br></code></pre></td></tr></table></figure>

<p><code>result::setProperty</code> 会调用 <code>groovy.lang.Closure#setProperty</code> 方法将用户传入的属性设置到 context 里，这个 context 是线程间共享的。<br>如果使用 rehydrate 返回的Closure对象，则属性会设置到各自的 Expando 对象里，不存在线程安全问题。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 设置属性</span><br>groovy.lang.Closure#setProperty<br>-&gt;<br><span class="hljs-comment">// 其中 delegate 就是上面的 Expando 对象。如果没设置，默认使用同一个 context</span><br>InvokerHelper.setProperty(<span class="hljs-built_in">this</span>.delegate, property, newValue);<br></code></pre></td></tr></table></figure>

<p>context 初始化逻辑：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 默认的 context 是在 GroovyShell 构造方法中初始化的，由于复用了同一个 GroovyShell，所以 context 是一份。</span><br><span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Binding context;<br><br><span class="hljs-keyword">public</span> <span class="hljs-title function_">GroovyShell</span><span class="hljs-params">(ClassLoader parent, Binding binding, <span class="hljs-keyword">final</span> CompilerConfiguration config)</span> &#123;<br>    <span class="hljs-built_in">this</span>.context = binding;<br>    <span class="hljs-built_in">this</span>.config = config;<br>&#125;<br></code></pre></td></tr></table></figure>

<p>如果使用有问题的代码执行，执行结果如下，由于没有使用 rehydrate 执行 copy 后的 Closure，导致各个线程之间属性会串：<br>可以看到 thread1 和 thread7 都将属性设置到了共享的 context <code>Bing@3954</code> 对象里，所以线程之间属性会串，导致执行结果有问题。<br><img src="/images/img-20240226163230.png"></p>
<p><img src="/images/img-20240226163334.png"></p>
<h3 id="Groovy表达式执行流程"><a href="#Groovy表达式执行流程" class="headerlink" title="Groovy表达式执行流程"></a>Groovy表达式执行流程</h3><p>Groovy 根据用户脚本，生成相关类代码，再编译成 Class 对象，然后创建闭包执行脚本。</p>
<p>调用api如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">GroovyShell</span> <span class="hljs-variable">SHELL</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">GroovyShell</span>();<br><br><span class="hljs-comment">// 1.解析 groovy script</span><br><span class="hljs-comment">// 生成scriptName;通过 groovy classloader 编译类,返回Class实例;通过Class反射创建Script对象返回</span><br><span class="hljs-type">Script</span> <span class="hljs-variable">script</span> <span class="hljs-operator">=</span> SHELL.parse(expression);<br><br><span class="hljs-comment">// 2.运行script。会调用到 会调用 Closure.run -&gt; Closure.call -&gt; 自定义script类的doCall方法</span><br>Closure&lt;?&gt; result = (Closure&lt;?&gt;) script.run();<br><br><span class="hljs-comment">// 3.重新实例化一个闭包对象,设置 delegate 为 Expando。这样闭包的执行环境将改变，不再依赖于原始闭包的环境</span><br>result.rehydrate(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Expando</span>(), <span class="hljs-literal">null</span>, <span class="hljs-literal">null</span>);<br>result.setResolveStrategy(Closure.DELEGATE_ONLY);<br><br><span class="hljs-comment">// 4.设置参数</span><br>map.forEach(result::setProperty);<br><br><span class="hljs-comment">// 5.运行闭包</span><br><span class="hljs-keyword">return</span> result.call().toString();<br></code></pre></td></tr></table></figure>

<h3 id="查看Groovy生成的类"><a href="#查看Groovy生成的类" class="headerlink" title="查看Groovy生成的类"></a>查看Groovy生成的类</h3><p>可以通过 arthas 反编译，查看 groovy 生成的 Script 类的代码。其中 doCall 方法就是用来具体处理 inline 表达式计算结果的方法。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><code class="hljs java">[arthas@<span class="hljs-number">97658</span>]$ sc Script*<br><br>[arthas@<span class="hljs-number">92326</span>]$ jad Script1$_run_closure1<br><br>ClassLoader:<br>+-groovy.lang.GroovyClassLoader$InnerLoader@2960c0ad<br>  +-groovy.lang.GroovyClassLoader@22c53a48<br>    +-jdk.internal.loader.ClassLoaders$AppClassLoader@251a69d7<br>      +-jdk.internal.loader.ClassLoaders$PlatformClassLoader@76f18a45<br><br>Location:<br>/groovy/shell<br><br><span class="hljs-comment">/*</span><br><span class="hljs-comment">* Decompiled with CFR.</span><br><span class="hljs-comment">*/</span><br><span class="hljs-keyword">import</span> groovy.lang.Closure;<br><span class="hljs-keyword">import</span> groovy.lang.MetaClass;<br><span class="hljs-keyword">import</span> java.lang.invoke.MethodHandles;<br><span class="hljs-keyword">import</span> org.codehaus.groovy.reflection.ClassInfo;<br><span class="hljs-keyword">import</span> org.codehaus.groovy.runtime.GStringImpl;<br><span class="hljs-keyword">import</span> org.codehaus.groovy.runtime.GeneratedClosure;<br><span class="hljs-keyword">import</span> org.codehaus.groovy.runtime.ScriptBytecodeAdapter;<br><span class="hljs-keyword">import</span> org.codehaus.groovy.vmplugin.v8.IndyInterface;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Script1</span>._run_closure1<br><span class="hljs-keyword">extends</span> <span class="hljs-title class_">Closure</span><br><span class="hljs-keyword">implements</span> <span class="hljs-title class_">GeneratedClosure</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-comment">/* synthetic */</span> ClassInfo $staticClassInfo;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">transient</span> <span class="hljs-comment">/* synthetic */</span> <span class="hljs-type">boolean</span> __$stMC;<br><br>    <span class="hljs-keyword">public</span> Script1._run_closure1(Object _outerInstance, Object _thisObject) &#123;<br>        <span class="hljs-built_in">super</span>(_outerInstance, _thisObject);<br>    &#125;<br><br>    <span class="hljs-comment">// call时会调用 doCall 方法</span><br>    <span class="hljs-keyword">public</span> Object <span class="hljs-title function_">doCall</span><span class="hljs-params">(Object it)</span> &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">GStringImpl</span>(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Object</span>[]&#123;IndyInterface.bootstrap(<span class="hljs-string">&quot;getProperty&quot;</span>, <span class="hljs-string">&quot;id&quot;</span>, <span class="hljs-number">12</span>, <span class="hljs-built_in">this</span>)&#125;, <span class="hljs-keyword">new</span> <span class="hljs-title class_">String</span>[]&#123;<span class="hljs-string">&quot;ds_&quot;</span>, <span class="hljs-string">&quot;&quot;</span>&#125;);<br>    &#125;<br><br>    <span class="hljs-keyword">protected</span> <span class="hljs-comment">/* synthetic */</span> MetaClass $getStaticMetaClass() &#123;<br>        <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.getClass() != Script1._run_closure1.class) &#123;<br>            <span class="hljs-keyword">return</span> ScriptBytecodeAdapter.initMetaClass(<span class="hljs-built_in">this</span>);<br>        &#125;<br>        <span class="hljs-type">ClassInfo</span> <span class="hljs-variable">classInfo</span> <span class="hljs-operator">=</span> $staticClassInfo;<br>        <span class="hljs-keyword">if</span> (classInfo == <span class="hljs-literal">null</span>) &#123;<br>            $staticClassInfo = classInfo = ClassInfo.getClassInfo(<span class="hljs-built_in">this</span>.getClass());<br>        &#125;<br>        <span class="hljs-keyword">return</span> classInfo.getMetaClass();<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-comment">/* synthetic */</span> MethodHandles.Lookup $getLookup() &#123;<br>        <span class="hljs-keyword">return</span> MethodHandles.lookup();<br>    &#125;<br>&#125;<br><br>Affect(row-cnt:<span class="hljs-number">1</span>) cost in <span class="hljs-number">468</span> ms.<br><br></code></pre></td></tr></table></figure>

<p>当执行 inline 表达式，调用 <code>Closure.call()</code> 方法时，会调用到 GString 构造方法,values 是传入的参数值替换占位符，strings 是 inline 表达式去除占位符部分，最终返回计算后的结果。<br><img src="/images/img-20240226165512.png"><br><img src="/images/img-20240226165424.png"></p>
<h2 id="PR"><a href="#PR" class="headerlink" title="PR"></a>PR</h2><p><a target="_blank" rel="noopener" href="https://github.com/apache/shardingsphere/pull/30295">https://github.com/apache/shardingsphere/pull/30295</a></p>
<link rel="stylesheet" href="https://fastly.jsdelivr.net/npm/markmap-toolbar@0.18.10/dist/style.css"><script src="https://fastly.jsdelivr.net/npm/d3@7"></script><script src="https://fastly.jsdelivr.net/npm/markmap-view@0.18.10"></script><script src="https://fastly.jsdelivr.net/npm/markmap-toolbar@0.18.10"></script>
<link rel="stylesheet" href="/css/markmap.css">

<script src="/js/markmap.js"></script>
</article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta"><i class="fas fa-circle-user fa-fw"></i>文章作者: </span><span class="post-copyright-info"><a href="https://flyingzc.github.io">flyingzc</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta"><i class="fas fa-square-arrow-up-right fa-fw"></i>文章链接: </span><span class="post-copyright-info"><a href="https://flyingzc.github.io/articles/shardingsphere/ShardingSphere-inline%E8%A1%A8%E8%BE%BE%E5%BC%8F%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E9%97%AE%E9%A2%98%E5%AE%9A%E4%BD%8D.html">https://flyingzc.github.io/articles/shardingsphere/ShardingSphere-inline%E8%A1%A8%E8%BE%BE%E5%BC%8F%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E9%97%AE%E9%A2%98%E5%AE%9A%E4%BD%8D.html</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta"><i class="fas fa-circle-exclamation fa-fw"></i>版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来源 <a href="https://flyingzc.github.io" target="_blank">flyingzc's blog</a>！</span></div></div><div class="tag_share"><div class="post-meta__tag-list"><a class="post-meta__tags" href="/tags/ShardingSphere/">ShardingSphere</a></div><div class="post-share"><div class="social-share" data-image="/images/zc-icon.jpeg" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/butterfly-extsrc/sharejs/dist/css/share.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/sharejs/dist/js/social-share.min.js" defer></script></div></div><nav class="pagination-post" id="pagination"></nav><div class="relatedPosts"><div class="headline"><i class="fas fa-thumbs-up fa-fw"></i><span>相关推荐</span></div><div class="relatedPosts-list"><a class="pagination-related" href="/articles/shardingsphere/ShardingSphere-Narayana-XA%E4%BA%8B%E5%8A%A1%E4%B8%8D%E5%9B%9E%E6%BB%9A%E9%97%AE%E9%A2%98%E5%AE%9A%E4%BD%8D.html" title="ShardingSphere Narayana XA 事务不回滚问题定位"><div class="cover" style="background: var(--default-bg-color)"></div><div class="info text-center"><div class="info-1"><div class="info-item-1"><i class="far fa-calendar-alt fa-fw"></i> 2024-02-29</div><div class="info-item-2">ShardingSphere Narayana XA 事务不回滚问题定位</div></div><div class="info-2"><div class="info-item-1">ShardingSphere Narayana XA 事务不回滚问题定位问题背景用户反馈，在使用 ShardingSphere + Narayana 执行 XA 事务时，发生报错：java.sql.SQLException: javax.transaction.RollbackException: TransactionImple.enlistResource - ARJUNA016064: The transaction is in an invalid state!。这个报错的含义简单来说就是这个事务已经在其他地方被标记成只可回滚了，不能再进行后续其他的操作。报错信息如下： 12345678910111213141516171819202122Caused by: java.sql.SQLException: javax.transaction.RollbackException: TransactionImple.enlistResource - ARJUNA016064: The transaction is in an invalid state!        at...</div></div></div></a><a class="pagination-related" href="/articles/shardingsphere/ShardingSphere-SQL%E8%A7%A3%E6%9E%90Issue%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B.html" title="ShardingSphere-SQL 解析 Issue 处理流程"><div class="cover" style="background: var(--default-bg-color)"></div><div class="info text-center"><div class="info-1"><div class="info-item-1"><i class="far fa-calendar-alt fa-fw"></i> 2024-03-05</div><div class="info-item-2">ShardingSphere-SQL 解析 Issue 处理流程</div></div><div class="info-2"><div class="info-item-1">ShardingSphere-SQL 解析 Issue 处理流程这是之前给社区写的 SQL 解析 Issue 的处理流程，可以帮助社区用户快速参与到 ShardingSphere-SQL 解析当中。 ShardingSphere SQL 解析 issue 列表 Issue 背景说明当前 Issue 使用自定义的爬虫脚本从对应的数据库官网上抓取 SQL Cases，然后交给 ShardingSphere 的解析引擎进行解析。对于解析失败的 SQL Case，每 3~5 条 SQL 作为一个 Issue。  由于是通过爬虫抓取的，所以不保证所有 SQL 本身就是正确的，请按照如下流程处理该 PR。 有些 SQL Case 可能已经在其他 PR 里被修复，对于已经可以执行的 Case 直接留言忽略即可。 如果该 SQL Case 已经可以成功运行，没有任何代码改动，那么不需要添加对应的 Test 断言文件。  总体流程 如果你打算参与该 Issue 的修复，欢迎在 Issue 下方留言，会有社区同学分配该 Issue。  比如留言：Hi, please assign this...</div></div></div></a><a class="pagination-related" href="/articles/shardingsphere/ShardingSphere-XA%E4%BA%8B%E5%8A%A1%E6%97%A0%E6%B3%95%E6%81%A2%E5%A4%8D%E9%97%AE%E9%A2%98.html" title="ShardingSphere-XA事务无法恢复问题定位"><div class="cover" style="background: var(--default-bg-color)"></div><div class="info text-center"><div class="info-1"><div class="info-item-1"><i class="far fa-calendar-alt fa-fw"></i> 2024-02-28</div><div class="info-item-2">ShardingSphere-XA事务无法恢复问题定位</div></div><div class="info-2"><div class="info-item-1">ShardingSphere-XA事务无法恢复问题定位问题背景在测试 ShardingSphere-Proxy 使用达梦数据库开启 XA 事务进行压测期间，kill 掉达梦数据库，等一段时间再重启达梦数据库，最终未完成的 XA 事务无法恢复，数据产生不一致。查看达梦上的 xid 一直有残留，没有被处理。使用 ShardingSphere-Proxy 结合其他主流数据库进行测试时无该问题。 通过日志可以看到在压测期间，挂掉达梦数据库之后，有大量连接报错。节选错误日志如下：比如下面这段日志，在执行 xa end 操作时，因为达梦数据库连接不上，达梦驱动报错如下： 1234567891011121314151617181920212223242526272829[WARN ] 2024-01-29 11:11:19.867 [Connection-82-ThreadExecutor] com.arjuna.ats.jta - ARJUNA016041: prepare on &lt; formatId=131077, gtrid_length=64, bqual_length=36,...</div></div></div></a></div></div></div><div class="aside-content" id="aside-content"><div class="card-widget card-info text-center"><div class="avatar-img"><img src="/images/zc-icon.jpeg" onerror="this.onerror=null;this.src='/img/friend_404.gif'" alt="avatar"/></div><div class="author-info-name">flyingzc</div><div class="author-info-description">不积跬步无以至千里<br/>不积小流无以成江海</div><div class="site-data"><a href="/archives/"><div class="headline">文章</div><div class="length-num">84</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">36</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">29</div></a></div><a id="card-info-btn" target="_blank" rel="noopener" href="https://github.com/FlyingZC"><i class="fab fa-github"></i><span>Follow Me</span></a><div class="card-info-social-icons"><a class="social-icon" href="https://github.com/FlyingZC" target="_blank" title="Github"><i class="fab fa-github" style="color: #24292e;"></i></a></div></div><div class="card-widget card-announcement"><div class="item-headline"><i class="fas fa-bullhorn fa-shake"></i><span>公告</span></div><div class="announcement_content">FlyingZC's Blog</div></div><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span><span class="toc-percentage"></span></div><div class="toc-content"><ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#ShardingSphere-inline%E8%A1%A8%E8%BE%BE%E5%BC%8F%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E9%97%AE%E9%A2%98%E5%AE%9A%E4%BD%8D"><span class="toc-number">1.</span> <span class="toc-text">ShardingSphere inline表达式线程安全问题定位</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%97%AE%E9%A2%98%E8%83%8C%E6%99%AF"><span class="toc-number">1.1.</span> <span class="toc-text">问题背景</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%97%AE%E9%A2%98%E5%8E%9F%E5%9B%A0"><span class="toc-number">1.2.</span> <span class="toc-text">问题原因</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%97%AE%E9%A2%98%E5%AE%9A%E4%BD%8D"><span class="toc-number">1.3.</span> <span class="toc-text">问题定位</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%9E%84%E9%80%A0%E6%B5%8B%E8%AF%95%E7%94%A8%E4%BE%8B%E5%B0%9D%E8%AF%95%E5%A4%8D%E7%8E%B0%E9%97%AE%E9%A2%98"><span class="toc-number">1.3.1.</span> <span class="toc-text">构造测试用例尝试复现问题</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%AF%B9%E8%B1%A1%E5%B1%9E%E6%80%A7%E7%BA%BF%E7%A8%8B%E9%97%B4%E5%85%B1%E4%BA%AB%E9%97%AE%E9%A2%98"><span class="toc-number">1.3.2.</span> <span class="toc-text">对象属性线程间共享问题</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%B7%BB%E5%8A%A0%E6%97%A5%E5%BF%97%E8%A7%82%E5%AF%9F%E6%89%A7%E8%A1%8C%E6%B5%81%E7%A8%8B"><span class="toc-number">1.3.3.</span> <span class="toc-text">添加日志观察执行流程</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#DEBUG%E6%A8%A1%E6%8B%9F%E5%A4%8D%E7%8E%B0%E6%B5%81%E7%A8%8B"><span class="toc-number">1.3.4.</span> <span class="toc-text">DEBUG模拟复现流程</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90"><span class="toc-number">1.4.</span> <span class="toc-text">问题分析</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#DEBUG%E5%88%86%E6%9E%90%E9%97%AE%E9%A2%98"><span class="toc-number">1.4.1.</span> <span class="toc-text">DEBUG分析问题</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Groovy%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%89%A7%E8%A1%8C%E6%B5%81%E7%A8%8B"><span class="toc-number">1.4.2.</span> <span class="toc-text">Groovy表达式执行流程</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%9F%A5%E7%9C%8BGroovy%E7%94%9F%E6%88%90%E7%9A%84%E7%B1%BB"><span class="toc-number">1.4.3.</span> <span class="toc-text">查看Groovy生成的类</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#PR"><span class="toc-number">1.5.</span> <span class="toc-text">PR</span></a></li></ol></li></ol></div></div><div class="card-widget card-recent-post"><div class="item-headline"><i class="fas fa-history"></i><span>最新文章</span></div><div class="aside-list"><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/articles/ignite/ignite03-transaction-begin.html" title="Apache Ignite 开启事务执行流程">Apache Ignite 开启事务执行流程</a><time datetime="2025-10-10T16:00:00.000Z" title="发表于 2025-10-11 00:00:00">2025-10-11</time></div></div><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/articles/ignite/ignite02-transaction.html" title="Apache Ignite 分布式事务原理">Apache Ignite 分布式事务原理</a><time datetime="2025-09-17T16:00:00.000Z" title="发表于 2025-09-18 00:00:00">2025-09-18</time></div></div><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/articles/ignite/ignite01-build.html" title="Apache Ignite IDEA 环境搭建">Apache Ignite IDEA 环境搭建</a><time datetime="2025-09-10T16:00:00.000Z" title="发表于 2025-09-11 00:00:00">2025-09-11</time></div></div><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/articles/druid/druid01-resultset-leak.html" title="ShardingSphere JDBC 使用 Druid 连接池 ResultSet 内存泄露分析">ShardingSphere JDBC 使用 Druid 连接池 ResultSet 内存泄露分析</a><time datetime="2025-06-30T16:00:00.000Z" title="发表于 2025-07-01 00:00:00">2025-07-01</time></div></div><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/articles/jmeter/jmeter01-shardingsphere.html" title="Jmeter 压测 ShardingSphere">Jmeter 压测 ShardingSphere</a><time datetime="2025-06-30T16:00:00.000Z" title="发表于 2025-07-01 00:00:00">2025-07-01</time></div></div></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div class="copyright">&copy;2020 - 2025 By flyingzc</div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="darkmode" type="button" title="日间和夜间模式切换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside-config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><button id="go-up" type="button" title="回到顶部"><span class="scroll-percent"></span><i class="fas fa-arrow-up"></i></button></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><div class="js-pjax"></div><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></div></body></html>